// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: hyuan

#include "coin2/exchange/gateio_futures/order_v4/parser.h"

#include <sstream>

#include <boost/algorithm/string.hpp>

#include "coin/proto/coin_query.pb.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/gateio_futures/api_order/native_private_client.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::gateio_futures::order_v4 {

namespace {

using coin::proto::FillType;

inline bool IsOrderUpdate(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  if (json.HasMember("channel") && json.HasMember("event") && json.HasMember("result")) {
    return (json["channel"] == "futures.orders") && (json["event"] == "update") &&
           json["result"].IsArray() && (json["result"].GetArray().Size() > 0);
  }
  return false;
}

inline bool IsTradeUpdate(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  if (json.HasMember("channel") && json.HasMember("event") && json.HasMember("result")) {
    return (json["channel"] == "futures.usertrades") && (json["event"] == "update") &&
           json["result"].IsArray() && (json["result"].GetArray().Size() > 0);
  }
  return false;
}

inline bool IsBalanceUpdate(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  if (json.HasMember("channel") && json.HasMember("event") && json.HasMember("result")) {
    return (json["channel"] == "futures.balances") && (json["event"] == "update") &&
           json["result"].IsArray() && (json["result"].GetArray().Size() > 0);
  }
  return false;
}

inline bool IsPositionUpdate(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  if (json.HasMember("channel") && json.HasMember("event") && json.HasMember("result")) {
    return (json["channel"] == "futures.positions") && (json["event"] == "update") &&
           json["result"].IsArray() && (json["result"].GetArray().Size() > 0);
  }
  return false;
}

}  // namespace

using coin::proto::AccountPosition;

auto GateioFuturesParser::native_currencies() const -> const CurrencyPairs& {
  static CurrencyPairs currency_pairs = [&]() -> auto{
    CurrencyPairs tmp;
    auto currency_set = symcache_->GetCurrencies();
    for (auto& currency : currency_set) {
      const auto& native_currency = currency->native_currency();
      tmp.emplace(std::make_pair(boost::to_lower_copy(native_currency), native_currency));
    }
    return tmp;
  }
  ();
  return currency_pairs;
}

int64_t GateioFuturesParser::GetTimestamp(const std::string& time_str) const {
  return stoll(time_str) * 1e9;
}

bool GateioFuturesParser::IsGateioFuturesErrMessage(const JsonValue& data) const {
  return data.IsObject() && data.HasMember("label");
}

int64_t GateioFuturesParser::GetProcOrderId(const JsonValue& data) const {
  if (!data.HasMember("text")) {
    return 0;
  }
  std::string_view proc_order_id = data["text"].GetString();
  if (!boost::starts_with(proc_order_id, "t-")) {
    LOG(ERROR) << "[GetProcOrderId] Invalid test format\n" << JsonToString(data);
    return 0;
  }
  proc_order_id = proc_order_id.substr(2, proc_order_id.size() - 2);
  return stoll(proc_order_id);
}

auto GateioFuturesParser::GetOrderSide(int64_t size) const -> OrderSide {
  if (size > 0) {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (size < 0) {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

auto GateioFuturesParser::GetOrderType(const std::string& type) const -> OrderType {
  return coin::proto::OrderType::LIMIT_ORDER;
}

void GateioFuturesParser::SetOrderStatus(
    const JsonValue& data,
    ProductOrderElement* product_order_element) {
  std::string_view status = data["status"].GetString();
  if (status == "open") {
    product_order_element->set_is_live(true);
  } else if (status == "finished") {
    product_order_element->set_is_live(false);
    std::string_view finish_as = data["finish_as"].GetString();
    if ((finish_as == "cancelled") || (finish_as == "liquidated") || (finish_as == "reduce_only") ||
        (finish_as == "position_closed")) {
      product_order_element->set_cancel_confirmed(true);
    } else if (finish_as == "filled") {
      product_order_element->set_fully_filled(true);
    }
  }
}

void GateioFuturesParser::ParseSubmitResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseSubmitResponse] failed [" << order_context->proc_order_id() << "], "
               << "AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if ((http_context->res.result_int() != 200) && (http_context->res.result_int() != 201) &&
      (http_context->res.result_int() != 202) && (http_context->res.result_int() != 204)) {
    LOG(ERROR) << "[ParseSubmitResponse] failed [" << order_context->proc_order_id() << "], "
               << "status code: " << http_context->res.result_int()
               << ", payload: " << http_context->res.body().data();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseSubmitResponse] failed [" << order_context->proc_order_id() << "], "
               << "Parse Error\n"
               << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (doc.IsObject() && IsGateioFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseSubmitResponse] failed [" << order_context->proc_order_id() << "], "
               << JsonToString(doc);
    std::string error_code;
    if (doc.HasMember("label")) {
      error_code = std::stoi(doc["label"].GetString());
    }
    if (error_code == "BALANCE_NOT_ENOUGH") {
      resp->set_error_code(coin::proto::OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND);
    } else if (error_code == "ORDER_NOT_FOUND") {
      resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NOT_FOUND);
    } else {
      resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    }
    resp->set_exchange_error_code(doc["label"].GetString());
    if (doc.HasMember("message")) {
      resp->set_exchange_error_msg(doc["message"].GetString());
    } else if (doc.HasMember("detail")) {
      resp->set_exchange_error_msg(doc["detail"].GetString());
    }
    resp->set_success(false);
    return;
  }

  resp->set_success(true);
  if (doc.HasMember("id")) {
    resp->set_exchange_order_id(doc["id"].GetString());
  } else {
    LOG(ERROR) << "[ParseSubmitResponse] Invalid data format\n" << JsonToString(doc);
  }
}

void GateioFuturesParser::ParseCancelResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseCancelResponse] failed [" << order_context->proc_order_id() << "], "
               << "AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if ((http_context->res.result_int() != 200) && (http_context->res.result_int() != 201) &&
      (http_context->res.result_int() != 202) && (http_context->res.result_int() != 204)) {
    LOG(ERROR) << "[ParseCancelResponse] failed [" << order_context->proc_order_id() << "], "
               << "status code: " << http_context->res.result_int()
               << ", payload: " << http_context->res.body().data();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseCancelResponse] failed [" << order_context->proc_order_id() << "], "
               << "Parse Error\n"
               << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (doc.IsObject() && IsGateioFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseCancelResponse] failed [" << order_context->proc_order_id() << "], "
               << JsonToString(doc);
    std::string error_code;
    if (doc.HasMember("label")) {
      error_code = std::stoi(doc["label"].GetString());
    }
    if (error_code == "BALANCE_NOT_ENOUGH") {
      resp->set_error_code(coin::proto::OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND);
    } else if (error_code == "ORDER_NOT_FOUND") {
      resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NOT_FOUND);
    } else {
      resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    }
    resp->set_exchange_error_code(doc["label"].GetString());
    if (doc.HasMember("message")) {
      resp->set_exchange_error_msg(doc["message"].GetString());
    } else if (doc.HasMember("detail")) {
      resp->set_exchange_error_msg(doc["detail"].GetString());
    }
    resp->set_success(false);
    return;
  }

  resp->set_success(true);
  if (doc.HasMember("id")) {
    resp->set_exchange_order_id(doc["id"].GetString());
  } else {
    LOG(ERROR) << "[ParseCancelResponse] Invalid data format\n" << JsonToString(doc);
  }
  resp->set_cancel_confirmed(true);
}

void GateioFuturesParser::ParseRestAccountBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing error.\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsGateioFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Error message.\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("currency") || !document.HasMember("total") ||
      !document.HasMember("available")) {
    LOG(ERROR) << "[ParseRestAccountBalance] Invalid data format.\n" << JsonToString(document);
    return;
  }

  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
auto GateioFuturesParser::ParseBalance(const JsonValue& doc) -> AccountBalance {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Gateio");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }

  return acc_balance;
}

bool GateioFuturesParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  /*
  {
    "total": "4.4516",
    "unrealised_pnl": "0",
    "available": "4.98",
    "order_margin": "0.1",
    "position_margin": "5.1",
    "point": "10000",
    "currency": "BTC",
    "in_dual_mode": false
  }
  */
  CurrencyBalance balance;
  balance.set_currency_native(doc["currency"].GetString());
  double unrealised_pnl = stod(doc["unrealised_pnl"].GetString());
  double total = stod(doc["total"].GetString()) + unrealised_pnl;
  double avail = stod(doc["available"].GetString());
  balance.set_total(total);
  balance.set_available(avail);
  balance.set_hold(total - avail);
  raw_balance_map->emplace(balance.currency_native(), balance);

  return true;
}

void GateioFuturesParser::ParseRestAccountPosition() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestAccountPosition] Parsing error.\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsGateioFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseRestAccountPosition] " << JsonToString(doc);
    return;
  }

  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseRestAccountPosition] " << JsonToString(doc);
    return;
  }

  auto account_position = ParsePosition(doc);
  if (!account_position.has_exchange() || !account_position.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_POSITION);
  *result_.mutable_account_position() = std::move(account_position);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountPosition GateioFuturesParser::ParsePosition(const JsonValue& doc) {
  RawPositionMap raw_position_map;
  auto success = ParseRawPosition(doc, &raw_position_map);
  if (!success) {
    return {};
  }

  AccountPosition acc_position;
  acc_position.set_exchange("Gateio");
  acc_position.set_market_type("Futures");
  for (const auto& native_symbol : symcache_->GetProductNative()) {
    if (raw_position_map.count(native_symbol) != 0) {
      continue;
    }
    auto* position = acc_position.add_each_position();
    position->set_long_position(0);
    position->set_short_position(0);
    position->set_net_position(0);
    position->set_available_long_position(0);
    position->set_available_short_position(0);
    position->set_native_symbol(native_symbol);

    DLOG(INFO) << "POS: " << native_symbol << " " << position->net_position();
  }

  for (auto& pair : raw_position_map) {
    auto* position = acc_position.add_each_position();
    *position = std::move(pair.second);
    position->set_native_symbol(pair.first);
    DLOG(INFO) << "POS: " << pair.first << " " << position->net_position();
  }

  return acc_position;
}

bool GateioFuturesParser::ParseRawPosition(
    const JsonValue& document,
    RawPositionMap* raw_position_map) {
  /*
  [
    {
      "user": 10000,
      "contract": "BTC_USDT",
      "size": -9440,
      "leverage": "0",
      "risk_limit": "100",
      "leverage_max": "100",
      "maintenance_rate": "0.005",
      "value": "2.497143098997",
      "margin": "4.431548146258",
      "entry_price": "3779.55",
      "liq_price": "99999999",
      "mark_price": "3780.32",
      "unrealised_pnl": "-0.000507486844",
      "realised_pnl": "0.045543982432",
      "history_pnl": "0",
      "last_close_pnl": "0",
      "realised_point": "0",
      "history_point": "0",
      "adl_ranking": 5,
      "pending_orders": 16,
      "close_order": {
        "id": 232323,
        "price": "3779",
        "is_liq": false
      },
      "mode": "single",
      "cross_leverage_limit": "0"
    }
  ]
  */
  for (const auto& data : document.GetArray()) {
    if (!data.HasMember("contract") || !data.HasMember("size")) {
      LOG(ERROR) << "[ParseRawPosition] Invalid position data\n" << JsonToString(document);
      continue;
    }
    auto native_symbol = data["contract"].GetString();
    auto& position = (*raw_position_map)[native_symbol];
    int64_t pos_amt = stoll(data["size"].GetString());
    if (pos_amt >= 0) {
      position.set_long_position(pos_amt);
    } else {
      position.set_short_position(-pos_amt);
    }
    position.set_net_position(pos_amt);
  }

  return true;
}

void GateioFuturesParser::ParseRestOrder(
    const JsonValue& data,
    ProductOrderElement* product_order_element) {
  /*
  [
    {
      "id": 15675394,
      "user": 100000,
      "contract": "BTC_USDT",
      "create_time": 1546569968,
      "size": 6024,
      "iceberg": 0,
      "left": 6024,
      "price": "3765",
      "fill_price": "0",
      "mkfr": "-0.00025",
      "tkfr": "0.00075",
      "tif": "gtc",
      "refu": 0,
      "is_reduce_only": false,
      "is_close": false,
      "is_liq": false,
      "text": "t-my-custom-id",
      "status": "finished",
      "finish_time": 1514764900,
      "finish_as": "cancelled"
    }
  ]
  */
  product_order_element->set_exchange_type(ExchangeType::Gateio);
  product_order_element->set_market_type(MarketType::Futures);
  product_order_element->set_api_version("v4");
  product_order_element->set_exchange_order_id(data["id"].GetString());
  product_order_element->set_symbol(data["contract"].GetString());
  product_order_element->set_price(stod(data["price"].GetString()));
  int64_t size = stoll(data["size"].GetString());
  product_order_element->set_qty(size);
  product_order_element->set_proc_order_id(GetProcOrderId(data));
  product_order_element->set_side(GetOrderSide(size));
  product_order_element->set_order_type(GetOrderType("limit"));
  product_order_element->set_timestamp(GetTimestamp(data["create_time"].GetString()));
  SetOrderStatus(data, product_order_element);
}

void GateioFuturesParser::ParseRestOpenOrdersInfo() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestOpenOrdersInfo] Parsing error.\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsGateioFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseRestOpenOrdersInfo] Error message.\n" << JsonToString(doc);
    return;
  }

  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseRestOpenOrdersInfo] Invalid data format.\n" << JsonToString(doc);
    return;
  }

  ProductOrderBundle* product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : doc.GetArray()) {
    if (!data.HasMember("contract")) {
      continue;
    }
    std::string native_symbol = data["contract"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    order->Clear();
    ParseRestOrder(data, order);
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void GateioFuturesParser::ParseRestHistOrdersInfo() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestHistOrdersInfo] Parsing error.\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsGateioFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseRestHistOrdersInfo] Error message.\n" << JsonToString(doc);
    return;
  }

  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseRestHistOrdersInfo] Invalid data format.\n" << JsonToString(doc);
    return;
  }

  ProductOrderBundle* product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : doc.GetArray()) {
    auto order = product_order_bundle->add_each_order();
    order->Clear();
    ParseRestOrder(data, order);
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void GateioFuturesParser::ParseRestFillInfo(
    const JsonValue& data,
    ProductFillElement* product_fill_element) {
  /*
  {
    "id": 121234231,
    "create_time": 1514764800.123,
    "contract": "BTC_USDT",
    "order_id": "21893289839",
    "size": 100,
    "price": "100.123",
    "role": "taker"
  }
  */
  product_fill_element->set_symbol(data["contract"].GetString());
  product_fill_element->set_fill_timestamp(GetTimestamp(data["create_time"].GetString()));
  product_fill_element->set_exchange_order_id(data["order_id"].GetString());
  product_fill_element->set_price(stod(data["price"].GetString()));
  int64_t size = stoll(data["size"].GetString());
  product_fill_element->set_qty(size);
  product_fill_element->set_side(GetOrderSide(size));
  product_fill_element->set_fill_id(data["id"].GetString());
  std::string role = data["role"].GetString();
  if (role == "maker") {
    product_fill_element->set_fill_type(FillType::MAKER_FILL_TYPE);
  } else if (role == "taker") {
    product_fill_element->set_fill_type(FillType::TAKER_FILL_TYPE);
  } else {
    product_fill_element->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
  }
}

void GateioFuturesParser::ParseRestFillInfoList() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestFillInfoList] Parsing error.\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsGateioFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseRestFillInfoList] Error message.\n" << JsonToString(doc);
    return;
  }

  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseRestFillInfoList] Invalid data format.\n" << JsonToString(doc);
    return;
  }

  ProductFillBundle* product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : doc.GetArray()) {
    auto fill = product_fill_bundle->add_each_fill();
    fill->Clear();
    ParseRestFillInfo(data, fill);
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void GateioFuturesParser::ParseRestTradingFee() {
  /*
  {
    "user_id": 10001,
    "taker_fee": "0.002",
    "maker_fee": "0.002",
    "futures_taker_fee": "-0.00025",
    "futures_maker_fee": "0.00075",
    "gt_discount": false,
    "gt_taker_fee": "0",
    "gt_maker_fee": "0",
    "loan_fee": "0.18",
    "point_type": "1"
  }
  */
  if (is_user_id_initialized_) {
    return;
  }

  // TODO(hyuan): Further test is required.
  // std::string user_id = "8324248";
  // mb_->PublishEvent("gateio_futures/user_id", &user_id);
  // is_user_id_initialized_ = true;
  // LOG(ERROR) << "[HY_ParseRestTradingFee] Got it.\n";
  // return;

  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestTradingFee] Parsing error.\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsGateioFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseRestTradingFee] Error message.\n" << JsonToString(doc);
    return;
  }

  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseRestTradingFee] Invalid data format.\n" << JsonToString(doc);
    return;
  }

  if (doc.HasMember("user_id")) {
    std::string user_id(doc["user_id"].GetString());
    if (!user_id.empty()) {
      mb_->PublishEvent("gateio_futures/user_id", &user_id);
      is_user_id_initialized_ = true;
    }
  } else {
    LOG(ERROR) << "[ParseRestTradingFee] Failed to fetch user id" << JsonToString(doc);
  }
}

void GateioFuturesParser::ParseWsOrderInfoList(const JsonDocument& document) {
  /*
  {
    "channel": "futures.orders",
    "event": "update",
    "time": 1541505434,
    "result": [
      {
        "contract": "BTC_USD",
        "create_time": 1628736847,
        "create_time_ms": 1628736847325,
        "fill_price": 40000.4,
        "finish_as": "filled",
        "finish_time": 1628736848,
        "finish_time_ms": 1628736848321,
        "iceberg": 0,
        "id": 4872460,
        "is_close": false,
        "is_liq": false,
        "is_reduce_only": false,
        "left": 0,
        "mkfr": -0.00025,
        "price": 40000.4,
        "refr": 0,
        "refu": 0,
        "size": 1,
        "status": "finished",
        "text": "-",
        "tif": "gtc",
        "tkfr": 0.0005,
        "user": "110xxxxx"
      }
    ]
  }
  */
  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : document["result"].GetArray()) {
    auto order = product_order_bundle->add_each_order();
    order->set_exchange_type(ExchangeType::Gateio);
    order->set_market_type(MarketType::Futures);
    order->set_api_version("v4");
    order->set_exchange_order_id(data["id"].GetString());
    order->set_symbol(data["contract"].GetString());
    order->set_price(stod(data["price"].GetString()));
    int64_t size = stoll(data["size"].GetString());
    order->set_qty(size);
    order->set_proc_order_id(GetProcOrderId(data));
    order->set_side(GetOrderSide(size));
    order->set_order_type(GetOrderType("limit"));
    order->set_timestamp(GetTimestamp(data["create_time"].GetString()));
    SetOrderStatus(data, order);
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void GateioFuturesParser::ParseWsTradeInfoList(const JsonDocument& document) {
  /*
  {
    "time": 1543205083,
    "channel": "futures.usertrades",
    "event": "update",
    "error": null,
    "result": [
      {
        "id": "3335259",
        "create_time": 1628736848,
        "create_time_ms": 1628736848321,
        "contract": "BTC_USD",
        "order_id": "4872460",
        "size": 1,
        "price": "40000.4",
        "role": "maker"
      }
    ]
  }
  */
  ProductFillBundle* product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : document["result"].GetArray()) {
    auto fill = product_fill_bundle->add_each_fill();
    fill->set_symbol(data["contract"].GetString());
    fill->set_fill_timestamp(GetTimestamp(data["create_time"].GetString()));
    fill->set_exchange_order_id(data["order_id"].GetString());
    fill->set_price(stod(data["price"].GetString()));
    int64_t size = stoll(data["size"].GetString());
    fill->set_qty(size);
    fill->set_side(GetOrderSide(size));
    fill->set_fill_id(data["id"].GetString());
    std::string role = data["role"].GetString();
    if (role == "maker") {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (role == "taker") {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void GateioFuturesParser::ParseWsBalanceInfoList(const JsonDocument& document) {
  /*
  {
    "channel": "futures.balances",
    "event": "update",
    "time": 1541505434,
    "result": [
      {
        "balance": 9.998739899488,
        "change": -0.000002074115,
        "text": "BTC_USD:3914424",
        "time": 1547199246,
        "time_ms": 1547199246123,
        "type": "fee",
        "user": "211xxx"
      }
    ]
  }
  */
  if (IsGateioFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseWsBalanceInfoList] Error message.\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("result") || !document["result"].IsArray()) {
    LOG(ERROR) << "[ParseWsBalanceInfoList] Invalid data format.\n" << JsonToString(document);
    return;
  }

  auto acc_balance = result_.mutable_account_balance();
  acc_balance->Clear();
  acc_balance->set_exchange("Gateio");
  acc_balance->set_market_type("Futures");
  bool acc_balance_found = false;
  // TODO(hyuan): Further test is required.
  for (const auto& data : document["result"].GetArray()) {
    if (!data.HasMember("currency") || !data.HasMember("total") || !data.HasMember("available")) {
      continue;
    }
    if (!symcache_->has_currency_native(data["currency"].GetString())) {
      continue;
    }
    auto* currency = symcache_->GetCurrencyByNative(data["currency"].GetString());
    auto* currency_balance = acc_balance->add_each_balance();
    currency_balance->set_currency(currency->currency());
    currency_balance->set_currency_native(currency->native_currency());
    auto total = stod(data["total"].GetString());
    auto avail = stod(data["available"].GetString());
    currency_balance->set_total(total);
    currency_balance->set_available(avail);
    currency_balance->set_hold(total - avail);
    acc_balance_found = true;
  }

  if (acc_balance_found) {
    result_.set_type(MsgType::ACCOUNT_BALANCE);
  }
}

void GateioFuturesParser::ParseWsPositionInfoList(const JsonDocument& document) {
  /*
  {
    "time": 1588212926,
    "channel": "futures.positions",
    "event": "update",
    "error": null,
    "result": [
      {
        "contract": "BTC_USD",
        "cross_leverage_limit": 0,
        "entry_price": 40000.36666661111,
        "history_pnl": -0.000108569505,
        "history_point": 0,
        "last_close_pnl": -0.000050123368,
        "leverage": 0,
        "leverage_max": 100,
        "liq_price": 0.1,
        "maintenance_rate": 0.005,
        "margin": 49.999890611186,
        "mode": "single",
        "realised_pnl": -1.25e-8,
        "realised_point": 0,
        "risk_limit": 100,
        "size": 3,
        "time": 1628736848,
        "time_ms": 1628736848321,
        "user": "110xxxxx"
      }
    ]
  }
  */
  if (IsGateioFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseWsPositionInfoList] Error message.\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("result") || !document["result"].IsArray()) {
    LOG(ERROR) << "[ParseWsPositionInfoList] Invalid data format.\n" << JsonToString(document);
    return;
  }

  RawPositionMap raw_position_map;
  if (!ParseRawPosition(document["result"], &raw_position_map)) {
    return;
  }

  auto acc_position = result_.mutable_account_position();
  acc_position->Clear();
  acc_position->set_exchange("Gateio");
  acc_position->set_market_type("Futures");
  bool acc_position_found = false;
  for (auto& pair : raw_position_map) {
    auto* position = acc_position->add_each_position();
    *position = std::move(pair.second);
    position->set_native_symbol(pair.first);
    DLOG(INFO) << "POS: " << pair.first << " " << position->net_position();
    acc_position_found = true;
  }

  if (acc_position_found) {
    result_.set_type(MsgType::ACCOUNT_POSITION);
  }
}

void GateioFuturesParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  DLOG(INFO) << "OG ws msg: " << JsonToString(document);
  if (IsOrderUpdate(document)) {
    ParseWsOrderInfoList(document);
  } else if (IsTradeUpdate(document)) {
    ParseWsTradeInfoList(document);
  } else if (IsBalanceUpdate(document)) {
    ParseWsBalanceInfoList(document);
  } else if (IsPositionUpdate(document)) {
    ParseWsPositionInfoList(document);
  }
}

auto GateioFuturesParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg.type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::MULTI_CANCEL_RESPONSE:
      // pass
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseRestOpenOrdersInfo();
      break;
    case OrderSubTopicId::REST_DONE_ORDERS:
      ParseRestHistOrdersInfo();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseRestFillInfoList();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseRestAccountBalance();
      break;
    case OrderSubTopicId::REST_ACCOUNT_POSITION:
      ParseRestAccountPosition();
      break;
    // case GateioFuturesOrderSubTopicId::REST_TRADING_FEE:
    case OrderSubTopicId::LISTEN_KEY:
      ParseRestTradingFee();
      break;
    default: {
      LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::gateio_futures::order_v4
